package com.xiaomaoguai.fcp.pre.kepler.mock.configuration;

import com.xiaomaoguai.fcp.pre.kepler.mock.properties.FeignMockConstants;
import com.xiaomaoguai.fcp.pre.kepler.mock.utils.FeignClassInfoHolder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * @author August.Zhang
 * @version v1.0.0
 * @date 2020/2/14 12:47
 * @since JDK 1.8
 */
@Configuration
public class FeignMocksRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware {

	private static final Logger log = LoggerFactory.getLogger(FeignMocksRegistrar.class);

	private ResourceLoader resourceLoader;

	private ClassLoader classLoader;

	private Environment environment;

	public FeignMocksRegistrar() {
	}

	/**
	 * 设置资源加载器
	 *
	 * @param resourceLoader 资源加载器
	 */
	@Override
	public void setResourceLoader(ResourceLoader resourceLoader) {
		this.resourceLoader = resourceLoader;
	}

	/**
	 * 设置Bean类装入器
	 *
	 * @param classLoader 类装入器
	 */
	@Override
	public void setBeanClassLoader(ClassLoader classLoader) {
		this.classLoader = classLoader;
	}

	/**
	 * 设置环境
	 *
	 * @param environment 环境
	 */
	@Override
	public void setEnvironment(Environment environment) {
		this.environment = environment;
	}

	/**
	 * 注册的Bean定义
	 *
	 * @param metadata 元数据
	 * @param registry 注册表
	 */
	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
		if (!isEnabled()) {
			log.info("Feign Mock not Enabled!");
			return;
		}
		log.info("Feign Mock  Enabled!");

		Set<String> basePackages = getBasePackages(metadata);

		log.debug("====>FeignClient scan package:{}", basePackages);

		Set<String> className = getFeignClassName(basePackages);

		cacheRequestMappingFeign(className);
	}

	/**
	 * 是否启用mock功能
	 *
	 * @return boolean
	 */
	protected boolean isEnabled() {
		return this.environment.getProperty(FeignMockConstants.FEIGN_MOCK_ENABLE, Boolean.class, Boolean.FALSE);
	}

	/**
	 * get请求处理程序映射
	 *
	 * @param className 类名
	 */
	private void cacheRequestMappingFeign(final Set<String> className) {
		for (String clazz : className) {
			try {
				Class<?> serviceInterface = ClassUtils.forName(clazz, classLoader);
				Method[] methods = serviceInterface.getMethods();
				if (methods.length != 0) {
					for (final Method method : methods) {
						if (method.isAnnotationPresent(RequestMapping.class)) {
							log.debug("====>register Feign-client:{}", clazz);
							FeignClassInfoHolder.addFeignClass(serviceInterface);
							break;
						}
					}
				}
			} catch (ClassNotFoundException e) {
				log.debug("====>register Feign-client error", e);
			}
		}
	}

	/**
	 * 会假装类名
	 *
	 * @param basePackages 基本包
	 * @return {@link java.util.Set<String>}
	 */
	private Set<String> getFeignClassName(final Set<String> basePackages) {
		Set<String> className = new HashSet<>();
		ClassPathScanningCandidateComponentProvider scanner = getScanner();
		scanner.setResourceLoader(this.resourceLoader);
		scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
		//获取每一个Feign接口
		for (final String basePackage : basePackages) {
			Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);
			for (BeanDefinition candidateComponent : candidateComponents) {
				if (candidateComponent instanceof AnnotatedBeanDefinition) {
					// verify annotated class is an interface
					AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
					AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
					Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface");
					className.add(candidateComponent.getBeanClassName());
				}
			}
		}
		return className;
	}

	/**
	 * 获得基础包
	 *
	 * @param importingClassMetadata 进口类元数据
	 * @return {@link java.util.Set<String>}
	 */
	protected Set<String> getBasePackages(AnnotationMetadata importingClassMetadata) {
		Set<String> basePackages = new HashSet<>();

		Map<String, Object> attrs = importingClassMetadata.getAnnotationAttributes(EnableFeignClients.class.getName());
		for (String pkg : (String[]) attrs.get("value")) {
			if (StringUtils.hasText(pkg)) {
				basePackages.add(pkg);
			}
		}

		if (basePackages.isEmpty()) {
			Map<String, Object> attributes = importingClassMetadata
					.getAnnotationAttributes(EnableFeignClients.class.getCanonicalName());

			for (String pkg : (String[]) attributes.get("value")) {
				if (StringUtils.hasText(pkg)) {
					basePackages.add(pkg);
				}
			}
			for (String pkg : (String[]) attributes.get("basePackages")) {
				if (StringUtils.hasText(pkg)) {
					basePackages.add(pkg);
				}
			}
			for (Class<?> clazz : (Class[]) attributes.get("basePackageClasses")) {
				basePackages.add(ClassUtils.getPackageName(clazz));
			}
		}

		if (basePackages.isEmpty()) {
			basePackages.add(ClassUtils.getPackageName(importingClassMetadata.getClassName()));
		}
		return basePackages;
	}

	/**
	 * 扫描注解了FeignClient的类
	 */
	private ClassPathScanningCandidateComponentProvider getScanner() {
		return new ClassPathScanningCandidateComponentProvider(false, this.environment) {

			@Override
			protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
				if (beanDefinition.getMetadata().isIndependent()) {
					if (beanDefinition.getMetadata().isInterface()
							&& beanDefinition.getMetadata().getInterfaceNames().length == 1
							&& Annotation.class.getName().equals(beanDefinition.getMetadata().getInterfaceNames()[0])) {
						try {
							Class<?> target = ClassUtils.forName(beanDefinition.getMetadata().getClassName(), FeignMocksRegistrar.this.classLoader);
							return !target.isAnnotation();
						} catch (Exception ex) {
							log.error("Could not load target class: " + beanDefinition.getMetadata().getClassName(), ex);
						}
					}
					return true;
				}
				return false;
			}
		};
	}

}
